package com;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;

import javax.net.ssl.SSLEngine;
import java.net.URI;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;

public class HttpClient {
    private int runningTask = 0;
    private int requestCount = 0;
    private int availableSize = 1000;
    private long timeMilliseconds = 0;
    private int timeout = 0;
    private AtomicLong atomicLong = new AtomicLong(0);
    private long simpleLong = 0;

    // Ensure exceptions during handshaking get propagated
    private static class ExceptionForwardingSslHandler extends SslHandler {

        ExceptionForwardingSslHandler(SSLEngine engine) {
            super(engine);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//            RequestInfo info = ctx.channel().attr(HttpClient.KEY).get();
//            if (info != null) {
//                info.handle.event(new State.Error(cause));
//            }
//            super.exceptionCaught(ctx, cause);
//            cause.printStackTrace(System.err);
        }
    }

    private void connect(final String host, final int port) throws Exception {
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(workerGroup);
        b.channel(NioSocketChannel.class);
        b.option(ChannelOption.SO_KEEPALIVE, false);
        b.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                SslContext clientContext = SslContextBuilder.forClient()
                        .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
                ch.pipeline().addLast("ssl", new ExceptionForwardingSslHandler(clientContext.newEngine(ByteBufAllocator.DEFAULT, host, port)));
                ch.pipeline().addLast(new HttpResponseDecoder());
                ch.pipeline().addLast(new HttpRequestEncoder());
                ch.pipeline().addLast(new HttpClientInboundHandler());
            }
        });

        URI uri = new URI("/seedweb/bin/user/_reg.php");
        final String msg = "aff=register2";
//        String postBody = "aff=register2\r\n";

        timeMilliseconds = new Date().getTime();

        for (int i = 0; i < 20000; i--) {
            Thread.sleep(1);
            long now = new Date().getTime();
            if (now - timeMilliseconds > 1000) {
                timeMilliseconds = now;
                timeout++;
                System.out.println(requestCount + " atomic: " + atomicLong.get() + " simple: " + simpleLong);
                if (timeout > 1000)
                    break;
            }
            if (atomicLong.get() > 1500) {
                continue;
            }

            final ByteBuf bb = Unpooled.wrappedBuffer(msg.getBytes("UTF-8"));
            final DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
                    uri.toASCIIString(), bb);

            request.headers().set(HttpHeaders.Names.HOST, host);
//        request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            request.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/x-www-form-urlencoded; charset=UTF-8");
            request.headers().set(HttpHeaders.Names.COOKIE, "");
            request.headers().set(HttpHeaders.Names.REFERER, "https://www.thatseed.org/seedweb/web/user/register.php?aff=register2");
            request.headers().set(HttpHeaders.Names.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) " +
                    "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36");
//        request.content().clear().writeBytes(postBody.getBytes("UTF-8"));
//            System.out.println(request.content().readableBytes());
            request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, request.content().readableBytes());

            ChannelFuture f = b.connect(host, port);
            atomicLong.incrementAndGet();
            simpleLong++;
            runningTask++;
//            System.out.println(i);
            f.addListener(new ChannelFutureListener() {
                public void operationComplete(final ChannelFuture f) throws Exception {
                    for (int j = 0; j < 1; j++) {
//                        System.out.println("before write: " + bf.readerIndex());
                        ChannelFuture fWAF = f.channel().writeAndFlush(request);
//                        System.out.println("after write: " + bf.readerIndex());
                        fWAF.addListener(new ChannelFutureListener() {
                            public void operationComplete(final ChannelFuture f) throws Exception {
//                                bf.resetReaderIndex();
//                                System.out.println(bf.readerIndex());
//                                bb.release();
                                requestCount++;
                            }
                        });
                    }
//                    f.channel().disconnect();
//                    ChannelFuture fCloseFuture = f.channel().closeFuture();
//                    fCloseFuture.addListener(new ChannelFutureListener() {
//                        public void operationComplete(final ChannelFuture f) {
//                            runningTask--;
////                            System.out.print(runningTask);
////                            System.out.println("closeFuture done: " + new Date().getTime());
//                        }
//                    });
                }
            });
            f.channel().closeFuture().addListener(new ChannelFutureListener() {
                public void operationComplete(final ChannelFuture f) throws Exception {
                    atomicLong.decrementAndGet();
                    simpleLong--;
                }
            });
        }

        int timeout = 5;
        while (runningTask > 0 && timeout > 0) {
            timeout--;
            Thread.sleep(1000);
        }
        System.out.println(requestCount);
        workerGroup.shutdownGracefully();
    }

    public static void main(String[] args) throws Exception {
        HttpClient client = new HttpClient();
        client.connect("www.thatseed.org", 443);
    }
}